/*********************************************
 * SDP/RTP HELP:
 * ==========================================
 * RTP port je konstantni 5400 (nastavuje jako parametr CSipClient2::Invite)...podobne jako v Connect jsou natvrdo porty pro SIP
 * k dispozici jsou metody:
 *  string GetFarRtpAddress() const;
 *  int GetFarRtpPort() const;
 ********************************************/


/*
 * TODO: zalezi na tom, jak chces, ale ja bych byt tebou nejdriv poresil ten hlas a pak osetroval ten bordel
 * ==========================================
 *  1. prenos hlasu (RTP)
 *  4. pri zaveseni SIPu musi zavesit i RTMP!!
 *  5. kdyz nebezi SIP server, tak se vyhodi a osetri vyjimka
 *     (na chybovy vystup se vypise zprava)
 *       - je potreba poslat neco Flashi, aby vedel, ze je konec
 *  6. stejny problem jako ve 4., pokud dojde k vyhozeni CSipException
 *     - ta je yhozena, pokud neni mone vytvorit SIP spojeni
 *     (napr. neni implementovan naky pozadavek, ktery server klade na klienta)
 *  7. volani smerem SIP -> RTMP
 */

#include "Session.h"

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include "PortManager.h"
#include "Rtmp.h"
#include "Debug.h"


CSession::CSession(int iSocket, CPortManager &pman) :
m_pman(pman),
m_rtmp(iSocket),
m_iSocket(iSocket)
{
  SetState(SS_STOPED);
  cout << "session created" << endl;
}

CSession::~CSession()
{
  DBGMSG("session destructor called");
}

bool CSession::Init()
{
  unsigned short _usPort;
/*  do {
    if(!m_pman.GetPort(false, _usPort))
      return false;
  } while(!m_sip.BindPort(_usPort));*/
  do {
    if(!m_pman.GetPort(true, _usPort)) {
      return false;
    }
  } while(!m_rtp.BindPort(_usPort));
  cout << "session init" << endl;
  return true;
}

void *SessionProc(void *pSession)
{
  cout << "session started" << endl;
  CSession _sess = *(CSession *)pSession;
  DBGMSG("session started");
  _sess.Run();
  DBGMSG("session ended");
  pthread_exit(NULL);
}

bool CSession::Start()
{
  cout << "try to start session" << endl;
  return (pthread_create(&m_thread, NULL, SessionProc, (void *)this) == 0);
}

void CSession::Run()
{
  cout << "Run.." << endl;
  int _iResult;
  fd_set _read, _write;
  timeval _timeout = {0, 0};

  SetState(SS_STARTED);
  m_rtmp.Init(&_read, &_write);
  m_sip.Init(&_read, &_write);
  m_rtp.Init(&_read, &_write);

  while(m_state != SS_STOPED)
  {
    // Test if Rtmp connection is alive
    if (!m_rtmp.IsAlive())
    {
        m_state = SS_STOPED;
        break;
    }

    _timeout.tv_sec = 1;
    FD_ZERO(&_read);
    FD_ZERO(&_write);


// setting fd_sets for read and write
    m_rtmp.SetFd();
    if (m_sip.IsConnected())
        m_sip.SetFd();
    m_rtp.SetFd();


    _iResult = select(FD_SETSIZE, &_read, &_write, NULL, &_timeout);
    if(_iResult > 0) {
      //DBGMSG("select session");
      switch(m_state) {
      case SS_STARTED:
        Handshake();
        break;
      case SS_CONNECTED:
        //DBGMSG("SS_CONNECTED");
        HandleEvent();
        break;
      case SS_DIALING:
        //DBGMSG("SS_DIALING");
        DialCall();
        break;
      case SS_RTMP_HANG_OUT:
        SipHangOut();
        break;
      case SS_SIP_HANG_OUT:
        RtmpHangOut();
        break;
      case SS_TALKING:
        ProcessCall();
        break;
      default:  // napr. SS_ERROR
        //DBGMSG("connection refused");
        m_state = SS_STOPED;
        break;
      }
    } else if(_iResult == 0) {
      DBGMSG("select timeout");
    } else {
      DBGMSG("select error");
    }
  }
  m_sip.Close();
  m_pman.ReleasePort(true, m_rtp.GetPortBase());
}

void CSession::Handshake()
{
  CConnectInfo _cinfo;
  switch(m_iMinorState) {
  case 0:
// provedeni handshake s rtmp klientem
    if(m_rtmp.Handshake())
    {
// handshake byl proveden uspesne
        m_iMinorState++;
    }
    break;
    
  case 1:
    {
// ziskani registracnich udaju od klienta
// musi byt po nacteni perzistentne ulozen v m_rtmp
      if(m_rtmp.GetConnectInfo(_cinfo))
      {
          // bindovani portu, nastaveni socketu
          if(!m_sip.Connect(_cinfo.GetSipUrl().substr(_cinfo.GetSipUrl().find('@') + 1), 5060, 10000))
          {
              SetState(SS_ERROR);
              m_iMinorState = 0;
              return;
          }
          //
          // registrace klienta na sip serveru - pridam do fronty pro zapsani do socketu a prejdu na dalsi minorstate, kde zajistim zbytek
          m_sip.Register(_cinfo.GetAuthName(), m_pman.GetValue(CE_SIP_ADDR), _cinfo.GetDisplayName(), _cinfo.GetAuthName(), _cinfo.GetPasswd());
          m_iMinorState++;
      }
    }
    break;

   case 2:
      cout << "minor state = 2\n" << endl;
      // registrace klienta na sip serveru - cekam, dokud neni registrace kompletni
      if(m_sip.ProcessResponse() == SipStatus::SS_REGISTERED)
      {
        if(m_rtmp.SetConnected())
        {
            m_iMinorState = 0;
            SetState(SS_CONNECTED);
            return;
        }
      }
      break;
  }

}

void CSession::HandleEvent()
{
    // zjisteni prichozich dat z rtmp
      if(m_rtmp.CheckInput())
      {
          cout << "m_rtmp.CheckInput()\n";
    // overeni, ze jde o informace pro odchozi volani z rtmp strany
    // tyto informace budou ulozeny v m_rtmp
        if(m_rtmp.GetMessageCode() == CRtmp::RTMP_DIAL)
        {

            cout << "Someone tries to call" << endl;
            //
            CDialInfo _dinfo;
            // ziskani informaci pro odchozi volani
            m_rtmp.GetDialInfo(_dinfo);
            m_rtmp.ProcessMessage();
            //
            size_t pos_at = _dinfo.GetContactURL().find('@');
            size_t pos_sip = _dinfo.GetContactURL().find("sip:");
            pos_sip = ((pos_sip == string::npos) ? 0 : pos_sip + 4);
            // inicializace odchoziho volani pres sip - hodim to do fronty pozadavku
            m_sip.Invite(_dinfo.GetContactURL().substr(pos_sip, pos_at - pos_sip), _dinfo.GetContactURL(),
                         _dinfo.GetContactURL().substr(pos_at + 1), m_pman.GetValue(CE_RTP_ADDR), m_rtp.GetPortBase(), m_iSocket);
            //
            SetState(SS_DIALING);
            return;
        }

        // pokud volajici zavesi ještě pri vyzvaneni
        if(m_rtmp.GetMessageCode() == CRtmp::RTMP_HANG_OUT)
        {
            m_sip.Cancel();
            SetState(SS_RTMP_HANG_OUT);
            return;
        }


        // jinak zpravu zpracuje ProcessMessage
        m_rtmp.ProcessMessage();

      }
     /* if(m_sip.ProcessResponse() == SipStatus::SS_HANGED_UP)
        {
            m_rtmp.SendBye();
            SetState(SS_SIP_HANG_OUT);
            return;
        }*/

}

void CSession::DialCall()
{
    // inicializace odchoziho volani pres sip - cekam, dokud to na druhy strane nekdo nezvedne nebo nezavesi jeste pri vyzvaneni
    int status = m_sip.ProcessResponse();
    cout << "\nSIP_STATUS: " << status << endl;
    //

    // zavesi
    if(m_rtmp.GetMessageCode() == CRtmp::RTMP_HANG_OUT)
    {
        m_sip.Cancel();
        SetState(SS_RTMP_HANG_OUT);
        return;
    }

    if(status == SipStatus::SS_HANGED_UP)
    {
        m_rtmp.SendRejected();
        SetState(SS_SIP_HANG_OUT);
        return;
    }
    // zvedne to
    if(status == SipStatus::SS_CALL_ACCEPTED)
    {
        m_rtmp.CallAccepted();
        cout << "\n[ PRIPOJENO ] RTP parametry - "
             << m_sip.GetFarRtpAddress() << ":"
             << m_sip.GetFarRtpPort() << "\n" << endl;


        m_rtp.SetPayloadType(false,96);
        //m_rtp.SetSendPayloadType(96);
        //m_rtp.SetRecvPayloadType(96);

        m_rtp.Connect(m_sip.GetFarRtpAddress(), m_sip.GetFarRtpPort());

        SetState(SS_TALKING);
    }
}

void CSession::RtmpHangOut()
{
    // pockej az flash potvrdi (jestli to potvrzuje)
    SetState(SS_CONNECTED);
}

void CSession::SipHangOut()
{
    SetState(SS_CONNECTED);
    if(m_sip.ProcessResponse() == SipStatus::SS_HANGED_UP)
    {
        SetState(SS_CONNECTED);
    }
}



void CSession::ProcessCall()
{
    cout << "Process call." << endl;
   string _strPacket;
// precteni dat z rtp
  if(m_rtp.ReadPacket(_strPacket, 160) > 0)
   {
    //m_codec.PcmToFlv();
    //for(int i = 0; i < _vstrPackets.size(); i++)
    //{
        cout << "New RTP data.. Size: " << _strPacket.size() << endl;
        m_rtmp.SendStream(char(0xB2)+_strPacket, 20);
    //}
  }

  string _strStreamBuffer;
  int _iTimestamp = 0;

  if(m_rtmp.CheckInput())
  {
      cout << "new rtmp packets." << endl;
    switch(m_rtmp.GetMessageCode())
    {
        case CRtmp::RTMP_STREAM:  //"normalni stream":
          if(m_rtmp.GetStream(_strStreamBuffer, _iTimestamp)) {
            _strStreamBuffer.erase(0,1);  // delete header
            cout << "RTP SendPacket: " <<  m_rtp.SendPacket(_strStreamBuffer,_iTimestamp) << endl;
            //m_rtmp.SendStream(_strStreamBuffer, 20); // echo server
           // m_codec.FlvToPcm();
            //m_rtp.SendPacket(_strStreamBuffer.c_str(), _strStreamBuffer.size(), _iTimestamp);
          }
          break;
        case CRtmp::RTMP_COMMAND:   //"nejake ridici informace":
            m_rtmp.ProcessMessage();
          break;
        case CRtmp::RTMP_HANG_OUT: //"ukonceni hovoru":

            m_sip.Bye();
            m_rtp.Disconnect();
            SetState(SS_RTMP_HANG_OUT);
            break;
        case CRtmp::RTMP_TCP_END: //"ukonceni TCP spojeni":
          SetState(SS_STOPED);
          return;
        default:
            m_rtmp.ProcessMessage();
          break;
    }
  }


  // sip zavesil
    if(m_sip.ProcessResponse() == SipStatus::SS_HANGED_UP)
    {
        cout << "new sip packet." << endl;
        m_rtmp.SendBye();
        m_rtp.Disconnect();
        SetState(SS_SIP_HANG_OUT);
        return;
    }



}

void CSession::SetState(SessionState state)
{
    // static by sice nemelo bejt potreba, protoze konstanty se ukladaj pri prekladu, ale pro jistotu...
    static const char *states[] =
    {
        "SS_STARTED", "SS_STOPED", "SS_CONNECTED", "SS_DIALING",
        "SS_RTMP_HANG_OUT", "SS_SIP_HANG_OUT", "SS_TALKING", "SS_ERROR"
    };
    //
    m_state = state;
    m_iMinorState = 0;
    cout << "\nServer is now in " << states[m_state] << " state\n" << endl;
}
